﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EntityComponentSystem.Framework;
using ECS.Demos.Tetris.Components;
using EntityComponentSystem.Framework.Math;
using ECS.Demos.Tetris.Behavoirs;
using EntityComponentSystem.Framework.Systems;

namespace ECS.Demos.Tetris.Systems
{
    public struct ConsoleTexture
    {
        public int Width;
        public int Height;
        public ColorEx[,] Colors;
        public char[,] Blocks;

        public static ConsoleTexture CreateTexture(int w, int h, ColorEx color, char block)
        {
            var texture = new ConsoleTexture();
            texture.Width = w;
            texture.Height = h;
            texture.Colors = new ColorEx[w, h];
            texture.Blocks = new char[w, h];
            texture.Clear(color, block);

            return texture;
        }

        public static ConsoleTexture CreateTexture(int w, int h, ColorEx[,] colors, char[,] blocks)
        {
            if (colors.Length != blocks.Length || w * h != colors.Length)
                throw new ArgumentOutOfRangeException("colors, blocks");

            var texture = new ConsoleTexture();
            texture.Width = w;
            texture.Height = h;
            texture.Colors = colors;
            texture.Blocks = blocks;

            return texture;
        }

        public void Clear()
        {
            for (var x = 0; x < Width; x++)
                for (var y = 0; y < Height; y++)
                {
                    Colors[x, y] = ColorEx.Transparent;
                    Blocks[x, y] = ' ';
                }
        }

        public void Clear(ColorEx color, char block)
        {
            for (var x = 0; x < Width; x++)
                for (var y = 0; y < Height; y++)
                {
                    Colors[x, y] = color;
                    Blocks[x, y] = block;
                }
        }

        public void Draw(ConsoleTexture texture, int x, int y, int w, int h, ColorEx color)
        {
            var sw = texture.Width / (float)w;
            var sh = texture.Height / (float)h;
            for (var xx = 0; xx < w; xx++)
                for (var yy = 0; yy < h; yy++)
                {
                    var dx = xx + x;
                    var dy = yy + y;
                    if (dx >= 0 && dy >= 0 && dx < Width && dy < Height)
                    {
                        var sx = (int)(sw * xx);
                        var sy = (int)(sh * yy);

                        if (texture.Colors[sx, sy] != ColorEx.Transparent)
                        {
                            this.Blocks[dx, dy] = texture.Blocks[sx, sy];
                            this.Colors[dx, dy] = texture.Colors[sx, sy] * color;
                        }
                    }
                }
        }

        public void DrawText(string text, int x, int y, ColorEx color)
        {
            for (var i = 0; i < text.Length; i++)
            {
                var dx = i + x;
                var dy = y;
                if (dx >= 0 && dy >= 0 && dx < Width && dy < Height)
                {
                    this.Blocks[dx, dy] = text[i];
                    this.Colors[dx, dy] = color;
                }
            }
        }
    }

    public interface IRenderSystem : IBehaviorSystem
    {
        void DrawTexture(string texture, int x, int y, int w, int h, ColorEx color);
        void DrawText(string text, int x, int y, ColorEx color);
    }

    public class ConsoleRenderSystem : BehaviorSystem, IRenderSystem
    {
        private Dictionary<string, ConsoleTexture> textures = new Dictionary<string, ConsoleTexture>();
        private ConsoleTexture backBuffer = ConsoleTexture.CreateTexture(80, 60, ColorEx.Transparent, ' ');
        private ConsoleTexture frontBuffer = ConsoleTexture.CreateTexture(80, 60, ColorEx.Transparent, ' ');
        private Dictionary<ColorEx, ConsoleColor> colorMap = new Dictionary<ColorEx, ConsoleColor>();

        public ConsoleRenderSystem()
        {
            this.Name = "rendersystem";
            Console.OutputEncoding = System.Text.Encoding.GetEncoding(1252);

            colorMap.Add(ColorEx.Black, ConsoleColor.Black);
            colorMap.Add(ColorEx.DarkBlue, ConsoleColor.DarkBlue);
            colorMap.Add(ColorEx.DarkGreen, ConsoleColor.DarkGreen);
            colorMap.Add(ColorEx.DarkCyan, ConsoleColor.DarkCyan);
            colorMap.Add(ColorEx.DarkRed, ConsoleColor.DarkRed);
            colorMap.Add(ColorEx.DarkMagenta, ConsoleColor.DarkMagenta);
            colorMap.Add(ColorEx.DarkYellow, ConsoleColor.DarkYellow);
            colorMap.Add(ColorEx.Gray, ConsoleColor.Gray);
            colorMap.Add(ColorEx.DarkGray, ConsoleColor.DarkGray);
            colorMap.Add(ColorEx.Blue, ConsoleColor.Blue);
            colorMap.Add(ColorEx.Green, ConsoleColor.Green);
            colorMap.Add(ColorEx.Cyan, ConsoleColor.Cyan);
            colorMap.Add(ColorEx.Red, ConsoleColor.Red);
            colorMap.Add(ColorEx.Magenta, ConsoleColor.Magenta);
            colorMap.Add(ColorEx.Yellow, ConsoleColor.Yellow);
            colorMap.Add(ColorEx.White, ConsoleColor.White);


            textures.Add("empty", ConsoleTexture.CreateTexture(1, 1, ColorEx.White, ' '));
            textures.Add("block", ConsoleTexture.CreateTexture(1, 1, ColorEx.White, '#'));
            textures.Add("left-frame-border", ConsoleTexture.CreateTexture(1, 1, ColorEx.White, (char)186));
            textures.Add("right-frame-border", ConsoleTexture.CreateTexture(1, 1, ColorEx.White, (char)186));
            textures.Add("top-frame-border", ConsoleTexture.CreateTexture(1, 1, ColorEx.White, (char)205));
            textures.Add("bottom-frame-border", ConsoleTexture.CreateTexture(1, 1, ColorEx.White, (char)205));

            textures.Add("top-left-frame-border", ConsoleTexture.CreateTexture(1, 1, ColorEx.White, (char)201));
            textures.Add("top-right-frame-border", ConsoleTexture.CreateTexture(1, 1, ColorEx.White, (char)187));
            textures.Add("bottom-left-frame-border", ConsoleTexture.CreateTexture(1, 1, ColorEx.White, (char)200));
            textures.Add("bottom-right-frame-border", ConsoleTexture.CreateTexture(1, 1, ColorEx.White, (char)188));

            Console.CursorVisible = false;
        }

        public override void Update(GameEngine ge)
        {
            for (var i = 0; i < 2; i++)
            {
                Console.SetCursorPosition(0, i);
                Console.Write("      ");
            }
            Console.SetCursorPosition(0, 0);

            backBuffer.Clear();

            base.Update(ge);

            for (var y = 0; y < backBuffer.Height; y++)
            {
                for (var x = 0; x < backBuffer.Width; x++)
                {
                    if ((backBuffer.Blocks[x, y] != frontBuffer.Blocks[x, y]) || (backBuffer.Colors[x, y] != frontBuffer.Colors[x, y]))
                    {
                        Console.SetCursorPosition(x, y);
                        Console.ForegroundColor = FindClosestColor(backBuffer.Colors[x, y]);
                        Console.Write(backBuffer.Blocks[x, y]);

                        frontBuffer.Blocks[x, y] = backBuffer.Blocks[x, y];
                        frontBuffer.Colors[x, y] = backBuffer.Colors[x, y];
                    }
                }
            }
        }

        private ConsoleColor FindClosestColor(ColorEx color)
        {
            var diff = 3f;
            var closet = ColorEx.Black;

            foreach (var c in colorMap.Keys)
            {
                var temp = 0f;
                temp += Math.Abs(color.r - c.r);
                temp += Math.Abs(color.g - c.g);
                temp += Math.Abs(color.b - c.b);

                if (temp < diff)
                {
                    closet = c;
                    diff = temp;
                }
            }

            return colorMap[closet];
        }

        public void DrawTexture(string textureName, int x, int y, int w, int h, ColorEx color)
        {
            var texture = textures[textureName];
            backBuffer.Draw(texture, x, y, w, h, color);
        }

        public void DrawText(string text, int x, int y, ColorEx color)
        {
            backBuffer.DrawText(text, x, y, color);
        }
    }
}
